package com.google.code.maven2.plugins.hotswap;

import com.google.code.maven2.plugins.hotswap.support.ClassFileFilter;
import com.google.code.maven2.plugins.hotswap.support.DirectoryFileFilter;
import com.pyx4j.log4j.MavenLogAppender;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;

import java.io.File;
import java.net.URL;

/**
 * <pre>
 * Used source from
 *
 * http://code.google.com/p/hotswap/source/browse/trunk/java/dak/ant/taskdefs/Hotswap.java
 *
 * To create a hotswap mojo.
 *
 * Also see, http://another-lazy-blogger.blogspot.com/2008/04/how-to-hotswap-classes-in-running.html
 * </pre>
 *
 * @author Mike Nereson
 * @goal hotswap
 * @phase install
 * @since 1.0
 */
public class HotSwapMojo
        extends AbstractMojo
{
    protected Log log;

    /**
     * @parameter expression="${host}"
     */
    protected String host;

    /**
     * @parameter expression="${port}"
     */
    protected String port;

    /**
     * @parameter expression="${name}"
     */
    protected String name;

    /**
     * @parameter expression="${project}"
     */
    protected org.apache.maven.project.MavenProject mavenProject;

    private static final String CLASSES = "classes";
    private static final String URL_FILE_PREFIX = "file:/";

    /**
     * @goal hotswap
     */
    public void execute()
            throws MojoExecutionException, MojoFailureException
    {
        /* Get maven log */
        MavenLogAppender.startPluginLog(this);
        log = getLog();

        try
        {
            final File artifactFile = getGeneratedArtifactFile();

            doExecute(artifactFile);
        }
        catch (Exception e)
        {
            /* this is an acceptable use case given that tomcat may not be running */
            log.error("Failed to hotswap: " + e.getMessage());
        }
        finally
        {
            MavenLogAppender.endPluginLog(this);
        }
    }

    /**
     * Begin hotswapping. The file is passed in to help with testing.
     *
     * TODO: get the classes directory from a Maven entitiy, not relative to a file.
     *
     * @param artifactFile File such as app-1.0.jar
     * @throws Exception
     */
    protected void doExecute(final File artifactFile)
            throws Exception
    {
        validateState();

        /* assumes the given file is an artifact in the target directory in order to find the classes directory */
        final File targetDirectory = artifactFile.getParentFile();
        final File classesDirectory = new File(targetDirectory, CLASSES);

        log.info("Hotswapping classes from " + classesDirectory.getAbsolutePath());

        /* connect should throw an exception when the connection can not be made */
        final HotSwapHelper hotSwapHelper = new HotSwapHelper(log);

        if (null != name && name.length() > 0)
        {
            hotSwapHelper.connect(name);
        }
        else
        {
            hotSwapHelper.connect(host, port);
        }

        log.info("\tConnected to " + host + ":" + port);

        try
        {
            /* begin processing directories and files under the classes directory */
            processHotswap(hotSwapHelper, classesDirectory);
        }
        finally
        {
            log.info("\tFinished processing classes");
            hotSwapHelper.disconnect();
        }
    }

    private void validateState()
    {
        boolean hasName = (null != name && name.length() > 0);

        boolean hasPort = (null != port && port.length() > 0);
        boolean hasHost = (null != host && host.length() > 0);

        if (hasName)
        {
            return;
        }

        if (hasPort && hasHost)
        {
            return;
        }

        throw new RuntimeException("HotSwap configuraiton must include (name) or (port and host)");

    }

    /**
     * Get the artifact File generated by this maven build.
     *
     * @return File the jar file
     */
    protected final File getGeneratedArtifactFile()
    {
        final Artifact artifact = mavenProject.getArtifact();
        final File artifactFile = artifact.getFile();

        log.debug("\tWorking with generated artifact " + artifact.getFile().getName());

        return artifactFile;
    }

    /**
     * Recursive method to hotstop all file and subdirectories under the given file(directory)
     *
     * @param hotSwapHelper
     * @param file          if this is a directory, all files within it, and all subdirectories will be processed
     * @throws Exception
     */
    private void processHotswap(final HotSwapHelper hotSwapHelper, final File file)
            throws Exception
    {
        log.debug("\t\tprocessHotswap");
        log.debug("\t\tFile " + file);

        if (DirectoryFileFilter.DIRECTORY.accept(file))
        {
            log.info("\t\t\tProcessing directory " + file.getAbsolutePath());

            final File[] childDirectories = file.listFiles(DirectoryFileFilter.DIRECTORY);

            for (final File subDirectory : childDirectories)
            {
                processHotswap(hotSwapHelper, subDirectory);
            }

            final File[] childFiles = file.listFiles(ClassFileFilter.CLAZZ);

            for (final File childFile : childFiles)
            {
                log.debug("\t\t\t\tSwapping file " + childFile.getName());
                processHotswap(hotSwapHelper, childFile);
            }
        }
        else if (ClassFileFilter.CLAZZ.accept(file))
        {
            String className = file.getAbsolutePath()
                    .replaceFirst(".*?classes[\\\\/](.*?)\\.class", "$1")
                    .replaceAll("[\\\\/]", ".");

            log.debug("\t\tHotswapping " + className);
            final URL url = new URL(URL_FILE_PREFIX + file.getAbsolutePath());

            hotSwapHelper.replace(url, className);
        }
    }
}
